from __future__ import unicode_literals

from django.db import models
import json
from django.core.cache import cache
from datetime import datetime
from django.utils.module_loading import import_string
from .exceptions import DependenceError
from .logHandler import JobLogHandler
from django.core.files.base import ContentFile
from django.utils.encoding import smart_str
import urllib
from django.utils.timezone import utc

# Create your models here.


class Task(models.Model):
    '''
    Task = module + kwargs + settings
    '''
    name = models.CharField(max_length=255, unique=True, db_index=True)
    module = models.CharField(max_length=2048, db_index=True)
    name_template = models.CharField(
        max_length=1024, default="{task_conf.name}")
    dependence = models.ManyToManyField('Task.Task', null=True, blank=True)
    raw_conf = models.TextField(default='{}')
    max_retry = models.IntegerField(default=3)
    live = models.BooleanField(default=True)

    def __unicode__(self):
        return self.name

    def save(self, *args, **kwargs):
        if self.pk:
            assert not self.dependence.filter(
                pk=self.pk), 'self dependence error'
        super(Task, self).save(*args, **kwargs)

    def create_job(self):
        today = datetime.today()
        job_name = self.name_template.format(task_conf=self)
        job = Job.objects.get_or_create(
            name=job_name, date=today, task=self)[0]

        if self.dependence:
            for task_dependence in self.dependence.all():
                job_dependence = task_dependence.create_job()
                job.dependence.add(job_dependence)
            job.save()

        return job

    @property
    def confdata(self):
        return json.loads(self.raw_conf)


class Job(models.Model):

    class Status:
        pending = 0
        waiting = 1
        running = 2
        error = 3
        success = 4

    STATUS_CHOICES = (
        (Status.pending, 'pending'),
        (Status.waiting, 'waiting'),
        (Status.running, 'running'),
        (Status.error, 'error'),
        (Status.success, 'success'),
    )

    date = models.DateField(db_index=True)
    dependence = models.ManyToManyField('Task.Job', null=True, blank=True)
    name = models.CharField(max_length=2048, db_index=True)
    task = models.ForeignKey(Task, related_name='jobs')
    log = models.TextField()
    status = models.IntegerField(
        default=Status.pending, choices=STATUS_CHOICES, db_index=True)
    retry = models.IntegerField(default=0)
    begin = models.DateTimeField(null=True)
    end = models.DateTimeField(null=True)

    class Meta:
        unique_together = (('task', 'date'),)

    def __unicode__(self):
        return self.name

    @property
    def logger(self):
        import logging

        logger = logging.getLogger(self.name)
        logger.setLevel(logging.DEBUG)
        if not logger.handlers:
            job_log_handler = JobLogHandler(self)
            logger.addHandler(job_log_handler)
        return logger

    def restart(self):
        self.status = self.Status.pending
        self.save()
        self.start()

    def start(self):
        if self.status not in (self.Status.waiting, self.Status.pending):
            return

        if self.status == self.Status.pending:
            dependence_is_complete = self.check_dependence_complete()
            if dependence_is_complete:
                self.status = self.Status.waiting

        if self.status == self.Status.waiting:
            try:
                self.run()
            except Exception as e:
                print 'run exception '
                print e
                if self.retry < self.task.max_retry:
                    self.retry+=1
                    self.status = self.Status.pending
                else:
                    self.logger.error(e)
                    self.status = self.Status.error
                self.save()
            self.logger.handlers[0].flush()

    def check_dependence_complete(self, save=True):
        uld_status = self.status
        dependence_status = set(
            [job.status for job in self.dependence.all() if job.status != self.Status.success])

        if self.Status.error in dependence_status:
            raise DependenceError()

        return not bool(dependence_status)

    def run(self, use_cache=True):
        print 'run'
        print self.pk
        self.status = self.Status.running
        self.begin = datetime.utcnow().replace(tzinfo=utc)
        self.save()

        module = import_string(self.task.module)

        cache_key = "job:{}".format(self.id)
        print cache_key

        if not use_cache or not cache.get(cache_key):
            print 'go'
            data = module(self)
            cache.set(cache_key, data, 43200)
        else:
            data = cache.get(cache_key)

        JobOutput.objects.filter(job=self).delete()
        if isinstance(data, dict):
            for key, value in data.items():
                if isinstance(value, str):
                    value = value.decode('utf-8')
                if not isinstance(value, basestring):
                    value = json.dumps(value, ensure_ascii=False)

                value = ContentFile(value.encode('utf-8'))

                job_output = JobOutput.objects.update_or_create(key=key, job=self)[
                    0]
                job_output.value.save(key, value)
        else:
            value = ContentFile(data)
            job_output = JobOutput.objects.update_or_create(job=self)[0]
            job_output.value.save(job_output.key, value)

        self.status = self.Status.success

        self.end = datetime.utcnow().replace(tzinfo=utc)
        self.save()


def job_file_path(instance, filename):
    job = instance.job
    date = job.date.strftime('%Y-%m-%d')
    task = job.task
    job_name = urllib.quote_plus(smart_str(job.name))
    name = "{}/{}/{}/{}".format(task.module, date, job_name, filename)
    return name


class JobOutput(models.Model):
    job = models.ForeignKey(Job, related_name='outputs')
    key = models.CharField(max_length=1024, default="output")
    value = models.FileField(upload_to=job_file_path, max_length=4096)

    def __unicode__(self):
        return self.key
